/**
 * Cost class encapsulate all relevant evaluation factors for determining the cost of splitting 
 * a context tree node according to a single split criteria.  
 * 
 * @author Yong Shin
 *
 */

public class Cost implements Comparable<Cost> {

	/** Tree node associated with Cost */
	private ContextTreeNode node;

	/** Split Criteria */
	private SplitCriteria criteria;

	/** Stores bit counts */
	private BitCounts[] bitCounts;
	
	/**
	 * Calculates adaptive encoding cost estimate.
	 * 
	 * @param oneBitCount number of one bits.
	 * @param zeroBitCount number of zero bits.
	 * @return adaptive cost estimate
	 */
	public static double adaptiveCostEstimate(int oneBitCount, int zeroBitCount) {
		
		double cost;
		double total = oneBitCount + zeroBitCount;
                if (total == 0) { 
                   cost = 0;
		} else if (oneBitCount == 0 || zeroBitCount == 0) {
			cost = (double) (.5 * log2(total) + .83);
		} else {
			double entropy = (oneBitCount/total) * log2 (total/oneBitCount) + 
							 (zeroBitCount/total) * log2 (total/zeroBitCount);
			cost = (double) (total * entropy + .5 * log2 (total) + .33);
		}
		
		return cost;
	}
        
        public static double adaptiveCostEstimate(BitCounts b) { 
            return adaptiveCostEstimate(b.oneBits, b.zeroBits);
        }

	/**
	 * Calculates firstOrderEntropy of a number of bits. 
	 * 
	 * @param oneBitCount number of one bits.
	 * @param zeroBitCount number of zero bits.
	 * @return first order entropy 
	 */
	public static double firstOrderEntropy(int oneBitCount, int zeroBitCount) {
		
		double total = oneBitCount + zeroBitCount;
		double entropy;
                if (total == 0) { 
                   entropy = 0;
		} else if (oneBitCount == 0 || zeroBitCount == 0) {
		   entropy = 0; 
		} else {
		    entropy = (oneBitCount/total) * log2 (total/oneBitCount) + 
			(zeroBitCount/total) * log2 (total/zeroBitCount);
		}		
		return (double) (total * entropy);
	}
        
        public static double firstOrderEntropy(BitCounts b) { 
            return firstOrderEntropy(b.oneBits, b.zeroBits);
        }
	
	/**
	 * Calculates log base 2 value of a number.
	 * @param a
	 * @return
	 */
	private static double log2(double a) {
		return Math.log(a) / Math.log(2);
	}

	/**
	 * Returns the cost associated with current ContextTreeNode.
	 * @return
	 */
	private double selfCost() {
		int totalOneBits = bitCounts[0].oneBits + bitCounts[1].oneBits;
		int totalZeroBits = bitCounts[0].zeroBits + bitCounts[1].zeroBits;
		return adaptiveCostEstimate(totalOneBits, totalZeroBits);
	}
	
	/**
	 * Returns the cost associated with left child ContextTreeNode.
	 * @return
	 */
	private double leftCost() {
		return adaptiveCostEstimate(bitCounts[0].oneBits, bitCounts[0].zeroBits);
	}
	
	/**
	 * Returns the cost associated with right child ContextTreeNode.
	 * @return
	 */
	private double rightCost() {
		return adaptiveCostEstimate(bitCounts[1].oneBits, bitCounts[1].zeroBits);
	}

	/**
	 * Returns split cost.
	 * @return
	 */
	public double splitCost() {
		return splitCost(node.getAvailableCriteria().size());
	}

	public static double splitCost(int numcriteria) {
		return (double) (2+log2(numcriteria));
        
        }
        
	public double splitCostUpperBound() {
                return Math.ceil(splitCost());
        }
        
	/**
	 * Creates a Cost object with spit criteria.
	 * 
	 * @param node context tree node associated with the cost.
	 * @param contextID context id of split criteria.
	 * @param bucketIndex bucket index of split criteria.
	 */
	public Cost(ContextTreeNode node, SplitCriteria criteria) {
		this.node = node;
		this.criteria = criteria;
		bitCounts = new BitCounts[2];
		bitCounts[0] = new BitCounts(0,0);
		bitCounts[1] = new BitCounts(0,0);
	}
	
	public ContextTreeNode getNode() {
		return node;
	}

	public SplitCriteria getSplitCriteria() {
		return criteria;
	}
	
	/**
	 * Returns the total cost savings associated with split.
	 * @return
	 */
	public double getSaving() {
		return selfCost() - leftCost() - rightCost() - splitCostUpperBound();
	}

	/**
	 * Accumulate bit counts.
	 * @param index
	 * @param bitCounts
	 */
	public void addBitCounts(int index, BitCounts bitCounts) {
		 this.bitCounts[index].add(bitCounts);
	}
	

	public int compareTo(Cost other) {
		return  Double.compare(other.getSaving(),getSaving());
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[(" + bitCounts[0].zeroBits + "," + bitCounts[0].oneBits + ")");
		sb.append("(" + bitCounts[1].zeroBits + "," + bitCounts[1].oneBits + ")] ");
		sb.append("Self=" + selfCost() + ",Left=" + leftCost() + ",Right=" + rightCost()
				+ ",Split=" + splitCost() + ",Saving=" + getSaving());
		return sb.toString();
	}

	public String toStringSummary() {
		StringBuilder sb = new StringBuilder();
		sb.append("[(" + bitCounts[0].zeroBits + "," + bitCounts[0].oneBits + ")");
		sb.append("(" + bitCounts[1].zeroBits + "," + bitCounts[1].oneBits + ")] ");
		sb.append("Saving=" + getSaving());
		return sb.toString();
	}
        
        public static void main (String args[]) {
	    if (args.length==2) {
		System.out.println("adaptiveCostEstimate(" + args[0] + 
				   "," + args[1] + ") = " + 
				   adaptiveCostEstimate( Integer.valueOf(args[0]),
							 Integer.valueOf(args[1])));
	    }
	    else {
		System.out.println("adaptiveCostEstimate(587133,2) = " + adaptiveCostEstimate(587133,2));
		System.out.println("adaptiveCostEstimate(587133,2) = " + adaptiveCostEstimate(587131,2));            }
        }
	
}
